
import {
  Blob,
  btoa,
  createImageBitmap,
  CSSStyleDeclaration,
  performance,
  document,
  DOMParser,
  EventTarget,
  fetch,
  Headers,
  HTMLCanvasElement,
Image,
  HTMLImageElement,
  ImageBitmap,
  location,
  navigator,
  Request,
  requestAnimationFrame,
  cancelAnimationFrame,
  Response,
  URL,
  window,
  self,
  WebAssembly,
  Worker,
  XMLHttpRequest,
ImageData,
TextDecoder,
  core
  } from 'dhtml-weixin';
import {
BufferGeometry,
Color,
FileLoader,
Float32BufferAttribute,
Group,
Loader,
LoaderUtils,
Mesh,
MeshPhongMaterial
} from '../../../three/Three';
import * as fflate from '../libs/fflate.module.js';

/**
* Description: Early release of an AMF Loader following the pattern of the
* example loaders in the three.js project.
*
* Usage:
*	const loader = new AMFLoader();
*	loader.load('/path/to/project.amf', function(objecttree) {
*		scene.add(objecttree);
*	});
*
* Materials now supported, material colors supported
* Zip support, requires fflate
* No constellation support (yet)!
*
*/

class AMFLoader extends Loader {

constructor( manager ) {

  super( manager );

}

load( url, onLoad, onProgress, onError ) {

  const scope = this;

  const loader = new FileLoader( scope.manager );
  loader.setPath( scope.path );
  loader.setResponseType( 'arraybuffer' );
  loader.setRequestHeader( scope.requestHeader );
  loader.setWithCredentials( scope.withCredentials );
  loader.load( url, function ( text ) {

    try {
      onLoad( scope.parse( text ) );

    } catch ( e ) {

      if ( onError ) {

        onError( e );

      } else {

        console.error( e );

      }

      scope.manager.itemError( url );

    }

  }, onProgress, onError );

}

parse( data ) {

  function loadDocument( data ) {

    let view = new DataView( data );
    const magic = String.fromCharCode( view.getUint8( 0 ), view.getUint8( 1 ) );

    if ( magic === 'PK' ) {

      let zip = null;
      let file = null;

      console.log( 'THREE.AMFLoader: Loading Zip' );

      try {

        zip = fflate.unzipSync( new Uint8Array( data ) ); // eslint-disable-line no-undef

      } catch ( e ) {

        if ( e instanceof ReferenceError ) {

          console.log( 'THREE.AMFLoader: fflate missing and file is compressed.' );
          return null;

        }

      }

      for ( file in zip ) {

        if ( file.toLowerCase().slice( - 4 ) === '.amf' ) {

          break;

        }

      }

      console.log( 'THREE.AMFLoader: Trying to load file asset: ' + file );
      view = new DataView( zip[ file ].buffer );

    }

    const fileText = LoaderUtils.decodeText( view );
    const xmlData = new DOMParser().parseFromString( fileText, 'application/xml' );

    if ( xmlData.documentElement.nodeName.toLowerCase() !== 'amf' ) {

      console.log( 'THREE.AMFLoader: Error loading AMF - no AMF document found.' );
      return null;

    }

    return xmlData;

  }

  function loadDocumentScale( node ) {

    let scale = 1.0;
    let unit = 'millimeter';

    if ( node.documentElement.attributes.unit !== undefined ) {

      unit = node.documentElement.attributes.unit.value.toLowerCase();

    }

    const scaleUnits = {
      millimeter: 1.0,
      inch: 25.4,
      feet: 304.8,
      meter: 1000.0,
      micron: 0.001
    };

    if ( scaleUnits[ unit ] !== undefined ) {

      scale = scaleUnits[ unit ];

    }

    console.log( 'THREE.AMFLoader: Unit scale: ' + scale );
    return scale;

  }

  function loadMaterials( node ) {

    let matName = 'AMF Material';
    const matId = node.attributes.id.textContent;
    let color = { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };

    let loadedMaterial = null;

    for ( let i = 0; i < node.childNodes.length; i ++ ) {

      const matChildEl = node.childNodes[ i ];

      if ( matChildEl.nodeName === 'metadata' && matChildEl.attributes.type !== undefined ) {

        if ( matChildEl.attributes.type.value === 'name' ) {

          matName = matChildEl.textContent;

        }

      } else if ( matChildEl.nodeName === 'color' ) {

        color = loadColor( matChildEl );

      }

    }

    loadedMaterial = new MeshPhongMaterial( {
      flatShading: true,
      color: new Color( color.r, color.g, color.b ),
      name: matName
    } );

    if ( color.a !== 1.0 ) {

      loadedMaterial.transparent = true;
      loadedMaterial.opacity = color.a;

    }

    return { id: matId, material: loadedMaterial };

  }

  function loadColor( node ) {

    const color = { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };

    for ( let i = 0; i < node.childNodes.length; i ++ ) {

      const matColor = node.childNodes[ i ];

      if ( matColor.nodeName === 'r' ) {

        color.r = matColor.textContent;

      } else if ( matColor.nodeName === 'g' ) {

        color.g = matColor.textContent;

      } else if ( matColor.nodeName === 'b' ) {

        color.b = matColor.textContent;

      } else if ( matColor.nodeName === 'a' ) {

        color.a = matColor.textContent;

      }

    }

    return color;

  }

  function loadMeshVolume( node ) {

    const volume = { name: '', triangles: [], materialid: null };

    let currVolumeNode = node.firstElementChild;

    if ( node.attributes.materialid !== undefined ) {

      volume.materialId = node.attributes.materialid.nodeValue;

    }

    while ( currVolumeNode ) {

      if ( currVolumeNode.nodeName === 'metadata' ) {

        if ( currVolumeNode.attributes.type !== undefined ) {

          if ( currVolumeNode.attributes.type.value === 'name' ) {

            volume.name = currVolumeNode.textContent;

          }

        }

      } else if ( currVolumeNode.nodeName === 'triangle' ) {

        const v1 = currVolumeNode.getElementsByTagName( 'v1' )[ 0 ].textContent;
        const v2 = currVolumeNode.getElementsByTagName( 'v2' )[ 0 ].textContent;
        const v3 = currVolumeNode.getElementsByTagName( 'v3' )[ 0 ].textContent;

        volume.triangles.push( v1, v2, v3 );

      }

      currVolumeNode = currVolumeNode.nextElementSibling;

    }

    return volume;

  }

  function loadMeshVertices( node ) {

    const vertArray = [];
    const normalArray = [];
    let currVerticesNode = node.firstElementChild;

    while ( currVerticesNode ) {

      if ( currVerticesNode.nodeName === 'vertex' ) {

        let vNode = currVerticesNode.firstElementChild;

        while ( vNode ) {

          if ( vNode.nodeName === 'coordinates' ) {

            const x = vNode.getElementsByTagName( 'x' )[ 0 ].textContent;
            const y = vNode.getElementsByTagName( 'y' )[ 0 ].textContent;
            const z = vNode.getElementsByTagName( 'z' )[ 0 ].textContent;

            vertArray.push( x, y, z );

          } else if ( vNode.nodeName === 'normal' ) {

            const nx = vNode.getElementsByTagName( 'nx' )[ 0 ].textContent;
            const ny = vNode.getElementsByTagName( 'ny' )[ 0 ].textContent;
            const nz = vNode.getElementsByTagName( 'nz' )[ 0 ].textContent;

            normalArray.push( nx, ny, nz );

          }

          vNode = vNode.nextElementSibling;

        }

      }

      currVerticesNode = currVerticesNode.nextElementSibling;

    }

    return { 'vertices': vertArray, 'normals': normalArray };

  }

  function loadObject( node ) {

    const objId = node.attributes.id.textContent;
    const loadedObject = { name: 'amfobject', meshes: [] };
    let currColor = null;
    let currObjNode = node.firstElementChild;

    while ( currObjNode ) {

      if ( currObjNode.nodeName === 'metadata' ) {

        if ( currObjNode.attributes.type !== undefined ) {

          if ( currObjNode.attributes.type.value === 'name' ) {

            loadedObject.name = currObjNode.textContent;

          }

        }

      } else if ( currObjNode.nodeName === 'color' ) {

        currColor = loadColor( currObjNode );

      } else if ( currObjNode.nodeName === 'mesh' ) {

        let currMeshNode = currObjNode.firstElementChild;
        const mesh = { vertices: [], normals: [], volumes: [], color: currColor };

        while ( currMeshNode ) {

          if ( currMeshNode.nodeName === 'vertices' ) {

            const loadedVertices = loadMeshVertices( currMeshNode );

            mesh.normals = mesh.normals.concat( loadedVertices.normals );
            mesh.vertices = mesh.vertices.concat( loadedVertices.vertices );

          } else if ( currMeshNode.nodeName === 'volume' ) {

            mesh.volumes.push( loadMeshVolume( currMeshNode ) );

          }

          currMeshNode = currMeshNode.nextElementSibling;

        }

        loadedObject.meshes.push( mesh );

      }

      currObjNode = currObjNode.nextElementSibling;

    }

    return { 'id': objId, 'obj': loadedObject };

  }

  const xmlData = loadDocument( data );
  let amfName = '';
  let amfAuthor = '';
  const amfScale = loadDocumentScale( xmlData );
  const amfMaterials = {};
  const amfObjects = {};
  const childNodes = xmlData.documentElement.childNodes;

  let i, j;

  for ( i = 0; i < childNodes.length; i ++ ) {

    const child = childNodes[ i ];

    if ( child.nodeName === 'metadata' ) {

      if ( child.attributes.type !== undefined ) {

        if ( child.attributes.type.value === 'name' ) {

          amfName = child.textContent;

        } else if ( child.attributes.type.value === 'author' ) {

          amfAuthor = child.textContent;

        }

      }

    } else if ( child.nodeName === 'material' ) {

      const loadedMaterial = loadMaterials( child );

      amfMaterials[ loadedMaterial.id ] = loadedMaterial.material;

    } else if ( child.nodeName === 'object' ) {

      const loadedObject = loadObject( child );

      amfObjects[ loadedObject.id ] = loadedObject.obj;

    }

  }

  const sceneObject = new Group();
  const defaultMaterial = new MeshPhongMaterial( { color: 0xaaaaff, flatShading: true } );

  sceneObject.name = amfName;
  sceneObject.userData.author = amfAuthor;
  sceneObject.userData.loader = 'AMF';

  for ( const id in amfObjects ) {

    const part = amfObjects[ id ];
    const meshes = part.meshes;
    const newObject = new Group();
    newObject.name = part.name || '';

    for ( i = 0; i < meshes.length; i ++ ) {

      let objDefaultMaterial = defaultMaterial;
      const mesh = meshes[ i ];
      const vertices = new Float32BufferAttribute( mesh.vertices, 3 );
      let normals = null;

      if ( mesh.normals.length ) {

        normals = new Float32BufferAttribute( mesh.normals, 3 );

      }

      if ( mesh.color ) {

        const color = mesh.color;

        objDefaultMaterial = defaultMaterial.clone();
        objDefaultMaterial.color = new Color( color.r, color.g, color.b );

        if ( color.a !== 1.0 ) {

          objDefaultMaterial.transparent = true;
          objDefaultMaterial.opacity = color.a;

        }

      }

      const volumes = mesh.volumes;

      for ( j = 0; j < volumes.length; j ++ ) {

        const volume = volumes[ j ];
        const newGeometry = new BufferGeometry();
        let material = objDefaultMaterial;

        newGeometry.setIndex( volume.triangles );
        newGeometry.setAttribute( 'position', vertices.clone() );

        if ( normals ) {

          newGeometry.setAttribute( 'normal', normals.clone() );

        }

        if ( amfMaterials[ volume.materialId ] !== undefined ) {

          material = amfMaterials[ volume.materialId ];

        }

        newGeometry.scale( amfScale, amfScale, amfScale );
        newObject.add( new Mesh( newGeometry, material.clone() ) );

      }

    }

    sceneObject.add( newObject );

  }

  return sceneObject;

}

}

export { AMFLoader };
